//-----------------------------------------------------------------------
// <copyright file="EventsAcceptanceTest.cs" company="NMock2">
//
//   http://www.sourceforge.net/projects/NMock2
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
// This is the easiest way to ignore StyleCop rules on this file, even if we shouldn't use this tag:
// <auto-generated />
//-----------------------------------------------------------------------
namespace NMock2.AcceptanceTests
{
    using System;
    using NUnit.Framework;

    [TestFixture]
    public class EventsAcceptanceTest : AcceptanceTestBase
    {
        private string _listenerMessage;

        public delegate void ListenerDelegate(string message);
        
        public interface IAnnouncer
        {
            event ListenerDelegate ListenerEvent;
        }

        public class Announcer : IAnnouncer
        {
            public virtual event ListenerDelegate ListenerEvent;
        }

        private void DummyListener(string message)
        {
            _listenerMessage = message;
        }

        [Test]
        public void CanExpectEventAddOnInterface()
        {
            AssertCanExpectEventAdd(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void CanExpectEventAddOnClass()
        {
            AssertCanExpectEventAdd(Mocks.NewMock<Announcer>());
        }

        private void AssertCanExpectEventAdd(IAnnouncer announcer)
        {
            Expect.Once.On(announcer).EventAdd("ListenerEvent", new ListenerDelegate(DummyListener));

            announcer.ListenerEvent += new ListenerDelegate(DummyListener);
        }

        [Test]
        public void CanExpectEventRemoveOnInterface()
        {
            AssertCanExpectEventRemove(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void CanExpectEventRemoveOnClass()
        {
            AssertCanExpectEventRemove(Mocks.NewMock<Announcer>());
        }

        private void AssertCanExpectEventRemove(IAnnouncer announcer)
        {
            Expect.Once.On(announcer).EventRemove("ListenerEvent", new ListenerDelegate(DummyListener));

            announcer.ListenerEvent -= new ListenerDelegate(DummyListener);
        }

        [Test]
        public void DelegatesCanBeComparedToEquality()
        {
            ListenerDelegate l1 = new ListenerDelegate(DummyListener);
            ListenerDelegate l2 = new ListenerDelegate(DummyListener);

            Assert.AreEqual(l1, l2);
        }

        [Test]
        public void EventsCanBeRaisedOnInterface()
        {
            AssertEventsCanBeRaisedDuringTests(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void EventsCanBeRaisedOnClass()
        {
            AssertEventsCanBeRaisedDuringTests(Mocks.NewMock<Announcer>());
        }

        private void AssertEventsCanBeRaisedDuringTests(IAnnouncer announcer)
        {
            _listenerMessage = null;
            
            Expect.Once.On(announcer).EventAdd("ListenerEvent", new ListenerDelegate(DummyListener));

            announcer.ListenerEvent += new ListenerDelegate(DummyListener);

            Fire.Event("ListenerEvent").On(announcer).With("Test Message");

            Assert.AreEqual("Test Message", _listenerMessage);
        }

        [Test]
        public void EventHandlingByMocksRespectsAdditionAndRemovalOfListenersOnInterface()
        {
            AssertEventHandlingByMocksRespectsAdditionAndRemovalOfListeners(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void EventHandlingByMocksRespectsAdditionAndRemovalOfListenersOnClass()
        {
            AssertEventHandlingByMocksRespectsAdditionAndRemovalOfListeners(Mocks.NewMock<Announcer>());
        }

        private void AssertEventHandlingByMocksRespectsAdditionAndRemovalOfListeners(IAnnouncer announcer)
        {
            _listenerMessage = "original";

            Fire.Event("ListenerEvent").On(announcer).With("this should do nothing");

            Assert.AreEqual("original", _listenerMessage);

            Expect.Once.On(announcer).EventAdd("ListenerEvent", new ListenerDelegate(DummyListener));
            announcer.ListenerEvent += new ListenerDelegate(DummyListener);

            Fire.Event("ListenerEvent").On(announcer).With("changed");

            Assert.AreEqual("changed", _listenerMessage);

            Expect.Once.On(announcer).EventRemove("ListenerEvent", new ListenerDelegate(DummyListener));
            announcer.ListenerEvent -= new ListenerDelegate(DummyListener);

            Fire.Event("ListenerEvent").On(announcer).With("something completely different");

            Assert.AreEqual("changed", _listenerMessage);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void InvocationOfNonexistingEventThrowArgumentExceptionOnInterface()
        {
            AssertInvocationOfNonexistingEventThrowArgumentException(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        [ExpectedException(typeof(ArgumentException))]
        public void InvocationOfNonexistingEventThrowArgumentExceptionOnClass()
        {
            AssertInvocationOfNonexistingEventThrowArgumentException(Mocks.NewMock<Announcer>());
        }

        private void AssertInvocationOfNonexistingEventThrowArgumentException(IAnnouncer announcer)
        {
            Fire.Event("NonexistingEvent").On(announcer).With("test");
        }

        [Test]
        public void RegistrationOfNonExistingEventThrowsArgumentExceptionOnInterface()
        {
            AssertRegistrationOfNonExistingEventThrowsArgumentException(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void RegistrationOfNonExistingEventThrowsArgumentExceptionOnClass()
        {
            AssertRegistrationOfNonExistingEventThrowsArgumentException(Mocks.NewMock<Announcer>());
        }

        private void AssertRegistrationOfNonExistingEventThrowsArgumentException(IAnnouncer announcer)
        {
            SkipVerificationForThisTest();
            
            try
            {
                Expect.Once.On(announcer).EventAdd("NonexistingEvent");

                Assert.Fail("exception should be thrown.");
            }
            catch (ArgumentException aex)
            {
                Assert.AreEqual("mock object announcer does not have an event named NonexistingEvent", aex.Message);
            }
        }

        [Test, CastleOnly]
        public void StackTraceNotLostOnInterface()
        {
            // InterfaceOnlyMockObjectFactory-generated mocks do not currently support this

            AssertStackTraceNotLost(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void StackTraceNotLostOnClass()
        {
            AssertStackTraceNotLost(Mocks.NewMock<Announcer>());
        }

        private void AssertStackTraceNotLost(IAnnouncer announcer)
        {
            Expect.Once.On(announcer).EventAdd("ListenerEvent");

            announcer.ListenerEvent += this.ThrowingListener;

            try
            {
                Fire.Event("ListenerEvent").On(announcer).With("test");

                Assert.Fail("exception should be thrown.");
            }
            catch (Exception e)
            {
                if (!e.ToString().StartsWith("System.Exception: test\r\n   at NMock2.AcceptanceTests.EventsAcceptanceTest.ThrowingListener(String message) in"))
                {
                    Assert.Fail("wrong error message. got: " + e);
                }
            }
        }

        private void ThrowingListener(string message)
        {
            throw new Exception(message);
        }
    }

    [TestFixture]
    public class NewSyntaxEventsAcceptanceTest : AcceptanceTestBase
    {
        private string _listenerMessage;

        public delegate void ListenerDelegate(string message);

        public interface IAnnouncer
        {
            event ListenerDelegate ListenerEvent;
        }

        public class Announcer : IAnnouncer
        {
            public virtual event ListenerDelegate ListenerEvent;
        }

        private void DummyListener(string message)
        {
            _listenerMessage = message;
        }

        [Test]
        public void CanExpectEventAddOnInterface()
        {
            AssertCanExpectEventAdd(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void CanExpectEventAddOnClass()
        {
            AssertCanExpectEventAdd(Mocks.NewMock<Announcer>());
        }

        private void AssertCanExpectEventAdd(IAnnouncer announcer)
        {
            Expect.Once.On(announcer).EventAdd("ListenerEvent", new ListenerDelegate(DummyListener));

            announcer.ListenerEvent += new ListenerDelegate(DummyListener);
        }

        [Test]
        public void CanExpectEventRemoveOnInterface()
        {
            AssertCanExpectEventRemove(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void CanExpectEventRemoveOnClass()
        {
            AssertCanExpectEventRemove(Mocks.NewMock<Announcer>());
        }

        private void AssertCanExpectEventRemove(IAnnouncer announcer)
        {
            Expect.Once.On(announcer).EventRemove("ListenerEvent", new ListenerDelegate(DummyListener));

            announcer.ListenerEvent -= new ListenerDelegate(DummyListener);
        }

        [Test]
        public void DelegatesCanBeComparedToEquality()
        {
            ListenerDelegate l1 = new ListenerDelegate(DummyListener);
            ListenerDelegate l2 = new ListenerDelegate(DummyListener);

            Assert.AreEqual(l1, l2);
        }

        [Test]
        public void EventsCanBeRaisedOnInterface()
        {
            AssertEventsCanBeRaisedDuringTests(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void EventsCanBeRaisedOnClass()
        {
            AssertEventsCanBeRaisedDuringTests(Mocks.NewMock<Announcer>());
        }

        private void AssertEventsCanBeRaisedDuringTests(IAnnouncer announcer)
        {
            _listenerMessage = null;

            Expect.Once.On(announcer).EventAdd("ListenerEvent", new ListenerDelegate(DummyListener));

            announcer.ListenerEvent += new ListenerDelegate(DummyListener);

            Fire.On(announcer).Event("ListenerEvent").With("Test Message");

            Assert.AreEqual("Test Message", _listenerMessage);
        }

        [Test]
        public void EventHandlingByMocksRespectsAdditionAndRemovalOfListenersOnInterface()
        {
            AssertEventHandlingByMocksRespectsAdditionAndRemovalOfListeners(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void EventHandlingByMocksRespectsAdditionAndRemovalOfListenersOnClass()
        {
            AssertEventHandlingByMocksRespectsAdditionAndRemovalOfListeners(Mocks.NewMock<Announcer>());
        }

        private void AssertEventHandlingByMocksRespectsAdditionAndRemovalOfListeners(IAnnouncer announcer)
        {
            _listenerMessage = "original";

            Fire.On(announcer).Event("ListenerEvent").With("this should do nothing");

            Assert.AreEqual("original", _listenerMessage);

            Expect.Once.On(announcer).EventAdd("ListenerEvent", new ListenerDelegate(DummyListener));
            announcer.ListenerEvent += new ListenerDelegate(DummyListener);

            Fire.On(announcer).Event("ListenerEvent").With("changed");

            Assert.AreEqual("changed", _listenerMessage);

            Expect.Once.On(announcer).EventRemove("ListenerEvent", new ListenerDelegate(DummyListener));
            announcer.ListenerEvent -= new ListenerDelegate(DummyListener);

            Fire.On(announcer).Event("ListenerEvent").With("something completely different");

            Assert.AreEqual("changed", _listenerMessage);
        }

        [Test]
        [ExpectedException(typeof(ArgumentException))]
        public void InvocationOfNonexistingEventThrowArgumentExceptionOnInterface()
        {
            AssertInvocationOfNonexistingEventThrowArgumentException(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        [ExpectedException(typeof(ArgumentException))]
        public void InvocationOfNonexistingEventThrowArgumentExceptionOnClass()
        {
            AssertInvocationOfNonexistingEventThrowArgumentException(Mocks.NewMock<Announcer>());
        }

        private void AssertInvocationOfNonexistingEventThrowArgumentException(IAnnouncer announcer)
        {
            Fire.On(announcer).Event("NonexistingEvent").With("test");
        }

        [Test]
        public void RegistrationOfNonExistingEventThrowsArgumentExceptionOnInterface()
        {
            AssertRegistrationOfNonExistingEventThrowsArgumentException(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void RegistrationOfNonExistingEventThrowsArgumentExceptionOnClass()
        {
            AssertRegistrationOfNonExistingEventThrowsArgumentException(Mocks.NewMock<Announcer>());
        }

        private void AssertRegistrationOfNonExistingEventThrowsArgumentException(IAnnouncer announcer)
        {
            SkipVerificationForThisTest();

            try
            {
                Expect.Once.On(announcer).EventAdd("NonexistingEvent");

                Assert.Fail("exception should be thrown.");
            }
            catch (ArgumentException aex)
            {
                Assert.AreEqual("mock object announcer does not have an event named NonexistingEvent", aex.Message);
            }
        }

        [Test, CastleOnly]
        public void StackTraceNotLostOnInterface()
        {
            // InterfaceOnlyMockObjectFactory-generated mocks do not currently support this

            AssertStackTraceNotLost(Mocks.NewMock<IAnnouncer>());
        }

        [Test, Class]
        public void StackTraceNotLostOnClass()
        {
            AssertStackTraceNotLost(Mocks.NewMock<Announcer>());
        }

        private void AssertStackTraceNotLost(IAnnouncer announcer)
        {
            Expect.Once.On(announcer).EventAdd("ListenerEvent");

            announcer.ListenerEvent += this.ThrowingListener;

            try
            {
                Fire.On(announcer).Event("ListenerEvent").With("test");

                Assert.Fail("exception should be thrown.");
            }
            catch (Exception e)
            {
                if (!e.ToString().StartsWith("System.Exception: test\r\n   at NMock2.AcceptanceTests.NewSyntaxEventsAcceptanceTest.ThrowingListener(String message) in"))
                {
                    Assert.Fail("wrong error message. got: " + e);
                }
            }
        }

        private void ThrowingListener(string message)
        {
            throw new Exception(message);
        }

        private int fireCount;
        private int anotherFireCount;

        [Test, Class]
        public void RegisterUnregisterEventOnStub()
        {
            this.fireCount = 0;
            this.anotherFireCount = 0;

            IAnnouncer announcer = Mocks.NewMock<IAnnouncer>(DefinedAs.OfStyle(MockStyle.Stub));
            
            announcer.ListenerEvent += this.Listener;
            Fire.On(announcer).Event("ListenerEvent").With("hello");
            
            announcer.ListenerEvent -= this.Listener;
            Fire.On(announcer).Event("ListenerEvent").With("hello");
            
            announcer.ListenerEvent += this.AnotherListener;
            Fire.On(announcer).Event("ListenerEvent").With("hello");

            Assert.AreEqual(1, this.fireCount, "fireCount");
            Assert.AreEqual(1, this.anotherFireCount, "anotherFireCount");
        }

        private void Listener(string value)
        {
            this.fireCount++;    
        }

        private void AnotherListener(string value)
        {
            this.anotherFireCount++;
        }
    }
}
